/**
 * Copyright (C) 2006 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.opensymphony.xwork2.inject;

import java.util.concurrent.Callable;

/**
 * Scope of an injected objects. 注入对象的池(范围) 有6个：DEFAULT SINGLETON THREAD REQUEST SESSION WIZARD 附带一个接口 Strategy
 * 这些SCOPE常量其实就是不同的工厂 在JAVA编程思想中 P603 19.10 称之这种写法为 常量相关的方法
 * 
 * @author crazybob
 */
public enum Scope
{
    
    /**
     * One instance per injection. 这个Scope.DEFAULT的getClass是class com.opensymphony.xwork2.inject.Scope$1 要注意
     * scopeFactory只是这个DEFAULT的一个方法 下面的每个属性都会实现scopeFactory这个方法 这个在effective java上有提到 这个方法主要把factory处理了一把再扔出去
     * default的没处理 直接扔出去 SINGLETON 保证每个Container中一个factory实例
     */
    DEFAULT
    {
        @Override
        <T> InternalFactory<? extends T> scopeFactory(Class<T> type, String name, InternalFactory<? extends T> factory)
        {
            return factory;
        }
    },
    
    /**
     * One instance per container.
     */
    SINGLETON
    {
        @Override
        <T> InternalFactory<? extends T> scopeFactory(Class<T> type, String name,
            final InternalFactory<? extends T> factory)
        {
            return new InternalFactory<T>()
            {
                T instance;
                
                public T create(InternalContext context)
                {
                    synchronized (context.getContainer())
                    {
                        if (instance == null)
                        {
                            instance = factory.create(context);
                        }
                        return instance;
                    }
                }
                
                @Override
                public String toString()
                {
                    return factory.toString();
                }
            };
        }
    },
    
    /**
     * One instance per thread.
     * 
     * <p>
     * <b>Note:</b> if a thread local object strongly references its {@link Container}, neither the {@code Container}
     * nor the object will be eligible for garbage collection, i.e. memory leak.
     */
    THREAD
    {
        @Override
        <T> InternalFactory<? extends T> scopeFactory(Class<T> type, String name,
            final InternalFactory<? extends T> factory)
        {
            return new InternalFactory<T>()
            {
                final ThreadLocal<T> threadLocal = new ThreadLocal<T>();
                
                public T create(final InternalContext context)
                {
                    T t = threadLocal.get();
                    if (t == null)
                    {
                        t = factory.create(context);
                        threadLocal.set(t);
                    }
                    return t;
                }
                
                @Override
                public String toString()
                {
                    return factory.toString();
                }
            };
        }
    },
    
    /**
     * One instance per request.
     */
    REQUEST
    {
        @Override
        <T> InternalFactory<? extends T> scopeFactory(final Class<T> type, final String name,
            final InternalFactory<? extends T> factory)
        {
            return new InternalFactory<T>()
            {
                public T create(InternalContext context)
                {
                    Strategy strategy = context.getScopeStrategy();
                    try
                    {
                        return strategy.findInRequest(type, name, toCallable(context, factory));
                    }
                    catch (Exception e)
                    {
                        throw new RuntimeException(e);
                    }
                }
                
                @Override
                public String toString()
                {
                    return factory.toString();
                }
            };
        }
    },
    
    /**
     * One instance per session.
     */
    SESSION
    {
        @Override
        <T> InternalFactory<? extends T> scopeFactory(final Class<T> type, final String name,
            final InternalFactory<? extends T> factory)
        {
            return new InternalFactory<T>()
            {
                public T create(InternalContext context)
                {
                    Strategy strategy = context.getScopeStrategy();
                    try
                    {
                        return strategy.findInSession(type, name, toCallable(context, factory));
                    }
                    catch (Exception e)
                    {
                        throw new RuntimeException(e);
                    }
                }
                
                @Override
                public String toString()
                {
                    return factory.toString();
                }
            };
        }
    },
    
    /**
     * One instance per wizard.
     */
    WIZARD
    {
        @Override
        <T> InternalFactory<? extends T> scopeFactory(final Class<T> type, final String name,
            final InternalFactory<? extends T> factory)
        {
            return new InternalFactory<T>()
            {
                public T create(InternalContext context)
                {
                    Strategy strategy = context.getScopeStrategy();
                    try
                    {
                        return strategy.findInWizard(type, name, toCallable(context, factory));
                    }
                    catch (Exception e)
                    {
                        throw new RuntimeException(e);
                    }
                }
                
                @Override
                public String toString()
                {
                    return factory.toString();
                }
            };
        }
    };
    
    <T> Callable<? extends T> toCallable(final InternalContext context, final InternalFactory<? extends T> factory)
    {
        return new Callable<T>()
        {
            public T call()
                throws Exception
            {
                return factory.create(context);
            }
        };
    }
    
    /**
     * Wraps factory with scoping logic.
     */
    abstract <T> InternalFactory<? extends T> scopeFactory(Class<T> type, String name,
        InternalFactory<? extends T> factory);
    
    /**
     * Pluggable scoping strategy. Enables users to provide custom implementations of request, session, and wizard
     * scopes. Implement and pass to {@link Container#setScopeStrategy(com.opensymphony.xwork2.inject.Scope.Strategy)}.<br>
     * TODO: 这个Strategy是干嘛用的？？
     */
    public interface Strategy
    {
        
        /**
         * Finds an object for the given type and name in the request scope. Creates a new object if necessary using the
         * given factory.
         */
        <T> T findInRequest(Class<T> type, String name, Callable<? extends T> factory)
            throws Exception;
        
        /**
         * Finds an object for the given type and name in the session scope. Creates a new object if necessary using the
         * given factory.
         */
        <T> T findInSession(Class<T> type, String name, Callable<? extends T> factory)
            throws Exception;
        
        /**
         * Finds an object for the given type and name in the wizard scope. Creates a new object if necessary using the
         * given factory.
         */
        <T> T findInWizard(Class<T> type, String name, Callable<? extends T> factory)
            throws Exception;
    }
}
